﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace Security
{
    public class PasswordGenerator
    {
        public PasswordGenerator(string password)
        {
            _passwordHash = ComputeHash(password);
        }

        public string PasswordHash
        {
            get
            {
                return ConvertBytesToString(_passwordHash);
            }
        }

        public string GeneratePassword(PasswordCharset charset)
        {
            var pattern = CreatePattern(charset);
            return GeneratePassword(_passwordHash, pattern);
        }

        private bool HasFlag(PasswordCharset charset, PasswordCharset flag)
        {
            return (charset & flag) != PasswordCharset.None;
        }

        private string CreatePattern(PasswordCharset charset)
        {
            if (charset == PasswordCharset.None) throw new NotSupportedException("Empty charset is not supported.");

            var removedCharset = new HashSet<char>();

            if (!HasFlag(charset, PasswordCharset.Alphabet)) RemoveChar(removedCharset, AlphabetCharset);
            if (!HasFlag(charset, PasswordCharset.Number)) RemoveChar(removedCharset, NumberCharset);
            if (!HasFlag(charset, PasswordCharset.Symbol)) RemoveChar(removedCharset, SymbolCharset);

            var sb = new StringBuilder();
            foreach (var c in AllCharset)
            {
                if (!removedCharset.Contains(c))
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }

        private void RemoveChar(HashSet<char> removedCharset, string removed)
        {
            foreach (var c in removed)
            {
                removedCharset.Add(c);
            }
        }

        private byte[] ComputeHash(string password)
        {
            var md5 = new MD5CryptoServiceProvider();
            var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
            return hash;
        }

        private string ConvertBytesToString(byte[] hash)
        {
            var sb = new StringBuilder();
            foreach (var c in hash)
            {
                sb.Append(c.ToString("X2"));
            }
            return sb.ToString();
        }

        private string GeneratePassword(byte[] hash, string pattern)
        {
            var sb = new StringBuilder();
            foreach (var c in hash)
            {
                var index = c % pattern.Length;
                sb.Append(pattern[index]);
            }
            return sb.ToString();
        }

        private const string AllCharset = @"!""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
        private const string AlphabetCharset = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        private const string NumberCharset = @"0123456789";
        private const string SymbolCharset = @"!""#$%&'()*+,-./:;<=>?@[\]^_`{|}~";

        private byte[] _passwordHash;
    }

    public enum PasswordCharset
    {
        None = 0x00,
        Alphabet = 0x01,
        Number = 0x02,
        Symbol = 0x04,
        All = Alphabet | Number | Symbol
    }
}
